Skip to content

Fix the implementation of BlowFish to solve bugs concerning NUL bytes truncating strings#21675

Open
LamentXU123 wants to merge 10 commits intophp:masterfrom
LamentXU123:vul-2
Open

Fix the implementation of BlowFish to solve bugs concerning NUL bytes truncating strings#21675
LamentXU123 wants to merge 10 commits intophp:masterfrom
LamentXU123:vul-2

Conversation

@LamentXU123
Copy link
Copy Markdown
Contributor

@LamentXU123 LamentXU123 commented Apr 8, 2026

Now, since including \0 in password_hash is not allowed in PASSWORD_BCRYPT. It is reasonable to add checkers that, makes input that includes \0 in password_verify directly fails.

This PR align password_verify() with password_hash() for bcrypt by rejecting passwords containing NUL bytes before verification.

Because bcrypt treats passwords as NUL-terminated C strings, this avoids ambiguous verification behavior for inputs such as "foo\0bar". A regression test is included.

Fix #21673

To be short: password encrypted by BYCRYPT should never includes \0. So directly rejecting it is always correct. It also avoids ambiguous verification where something like this happens:

<?php
$hash = password_hash("secret", PASSWORD_BCRYPT);
var_dump(password_verify("secret" . chr(0) . "suffix", $hash)); //True, WTF?
?>

zend_long cost = PHP_PASSWORD_BCRYPT_COST;

if (memchr(ZSTR_VAL(password), '\0', ZSTR_LEN(password))) {
if (zend_str_has_nul_byte(password)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I also refactor this by the way.

@alecpl
Copy link
Copy Markdown

alecpl commented Apr 9, 2026

Shouldn't that be rather done in php_password_bcrypt_verify()?

@LamentXU123
Copy link
Copy Markdown
Contributor Author

Shouldn't that be rather done in php_password_bcrypt_verify()?

It is before, see the initial fix in 6f14bee

Actually I think that'd be better. Anyway, lets wait for code owners.

@Girgias
Copy link
Copy Markdown
Member

Girgias commented Apr 9, 2026

Can we not fix the blowfish implementation? It seems like it requires providing the length of the string all the way down to BF_set_key. Or does BlowFish specifically not support null bytes?

@LamentXU123
Copy link
Copy Markdown
Contributor Author

LamentXU123 commented Apr 9, 2026

Does BlowFish specifically not support null bytes?

Blowfish do support null bytes, but in php implementation it specifically doesn't. Any password with NUL will fail when hashing by using password_hash in bcrypt mode.

bugs with NUL bytes is inevitable since PHP is based on C string. But in this case, I think directly rejecting all inputs containing NUL bytes will be safer and always correct.

@Girgias
Copy link
Copy Markdown
Member

Girgias commented Apr 10, 2026

Does BlowFish specifically not support null bytes?

Blowfish do support null bytes, but in php implementation it specifically doesn't. Any password with NUL will fail when hashing by using password_hash in bcrypt mode.

bugs with NUL bytes is inevitable since PHP is based on C string. But in this case, I think directly rejecting all inputs containing NUL bytes will be safer and always correct.

Sure, but we "own" the implementation of BF, so why not actually fix the implementation? I'm happy to reject them as a bug fix that is backported, but in the long run we should fix the implementation.

@LamentXU123
Copy link
Copy Markdown
Contributor Author

Does BlowFish specifically not support null bytes?

Blowfish do support null bytes, but in php implementation it specifically doesn't. Any password with NUL will fail when hashing by using password_hash in bcrypt mode.
bugs with NUL bytes is inevitable since PHP is based on C string. But in this case, I think directly rejecting all inputs containing NUL bytes will be safer and always correct.

Sure, but we "own" the implementation of BF, so why not actually fix the implementation? I'm happy to reject them as a bug fix that is backported, but in the long run we should fix the implementation.

In the long run we should. But for now, password_hash does not accept password containing NUL, so fixing the implementation would create a BC break on this which requires a RFC. I would be happy to write such RFC in the future but I think this bug could be fix before we get it passed.

@Girgias
Copy link
Copy Markdown
Member

Girgias commented Apr 10, 2026

I don't think fixing the implementation to be correct is a BC break.
Your bug fix is a BC break by this logic...

@LamentXU123
Copy link
Copy Markdown
Contributor Author

I don't think fixing the implementation to be correct is a BC break.

That'd be good. I will work on this hours later.

I assume that the best practice here would be passing the correct length throughout the implementation so the strings will not be cut by NUL

@Girgias
Copy link
Copy Markdown
Member

Girgias commented Apr 10, 2026

I don't think fixing the implementation to be correct is a BC break.

That'd be good. I will work on this hours later.

I assume that the best practice here would be passing the correct length throughout the implementation so the strings will not be cut by NUL

Correct :)

@LamentXU123 LamentXU123 changed the title Fix GH-21673: password_verify() failed to verify bcrypt passwords containing null bytes Fix the implementation of BlowFish to solve bugs concerning NUL bytes truncating strings Apr 10, 2026
Comment on lines 209 to 210
Z_PARAM_STRING(str, str_len)
Z_PARAM_STRING(salt_in, salt_in_len)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want these to be Z_PARAM_PATH if we use the system crypt. Or Maybe it should indeed always be PATH so that the behaviour is consistent with the system API...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want these to be Z_PARAM_PATH

I implement the bcrypt() to reject NUL byte, and make password_* not to.

Not sure about this change.

LamentXU123 and others added 2 commits April 13, 2026 18:40
Co-authored-by: Gina Peter Banyard <girgias@php.net>
Copy link
Copy Markdown
Member

@Girgias Girgias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure how I feel about this either. Maybe @TimWolla has an opinion?

@TimWolla TimWolla self-requested a review April 13, 2026 14:59
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a vendored library and must not be touched.

@TimWolla
Copy link
Copy Markdown
Member

Or does BlowFish specifically not support null bytes?

It's not BlowFish, it's Bcrypt. And no, most Bcrypt implementation don't support NULs.

@ndossche
Copy link
Copy Markdown
Member

The idiotic thing about how PHP implements password hashing is that it doesn't always use the builtin bcrypt implementation: it can defer to crypt_r which doesn't support NUL bytes. So if you want to fix this you'll need to ditch that other code path but that runs into political issues about "bundling too much code".

@bukka
Copy link
Copy Markdown
Member

bukka commented Apr 13, 2026

I guess the idea was that crypt implementation would be more secure but not sure if there's much value in it.

@bukka
Copy link
Copy Markdown
Member

bukka commented Apr 13, 2026

I'm also not sure if there's much value in allowing nul bytes in password. I think the initial idea of not allowing in the verify was more reasonable. That was sort of the plan when we were handling GHSA-h746-cjrr-wfmr - prohibit the hashing as low security issue and add warning for verify in master.

@LamentXU123
Copy link
Copy Markdown
Contributor Author

I don't have strong opinions on both solutions, but I would revert back to the initial fix I guess if we are following GHSA-h746-cjrr-wfmr

plus: Is this issue worth a separate security entry? I mean that password_verify doesn't check if the input has NUL bytes could have low impact on some login scenarios.

@TimWolla
Copy link
Copy Markdown
Member

I mean that password_verify doesn't check if the input has NUL bytes could have low impact on some login scenarios.

Please explain the impact you're seeing. I'm seeing none and I don't think this change is necessary either. For password_hash it's useful to not accidentally generate “weak hashes”, but for password_verify rejecting NULs will just break some working logic without any upside.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

password_verify() failed to verify bcrypt passwords containing null bytes

7 participants